package utils

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"regexp"
	"strings"
	"time"
)

// LogLevel represents different logging levels
type LogLevel int

const (
	LogLevelDebug LogLevel = iota
	LogLevelInfo
	LogLevelWarn
	LogLevelError
)

// String returns the string representation of LogLevel
func (ll LogLevel) String() string {
	switch ll {
	case LogLevelDebug:
		return "DEBUG"
	case LogLevelInfo:
		return "INFO"
	case LogLevelWarn:
		return "WARN"
	case LogLevelError:
		return "ERROR"
	default:
		return "UNKNOWN"
	}
}

// AuthLogger provides secure logging for authentication operations
type AuthLogger struct {
	level          LogLevel
	enableDebug    bool
	logger         *log.Logger
	tokenMasker    *TokenMasker
	performanceLog bool
}

// NewAuthLogger creates a new authentication logger
func NewAuthLogger(config *DynamicAuthConfig) *AuthLogger {
	level := parseLogLevel(config.LogLevel)
	
	logger := log.New(os.Stdout, "[AUTH] ", log.LstdFlags|log.Lmicroseconds)
	
	return &AuthLogger{
		level:          level,
		enableDebug:    config.EnableDebugLogs,
		logger:         logger,
		tokenMasker:    NewTokenMasker(),
		performanceLog: level <= LogLevelDebug,
	}
}

// LogDebug logs debug-level messages (only if debug is enabled)
func (al *AuthLogger) LogDebug(sessionID, message string, data map[string]interface{}) {
	if !al.enableDebug || al.level > LogLevelDebug {
		return
	}
	al.logWithLevel(LogLevelDebug, sessionID, message, data)
}

// LogInfo logs info-level messages
func (al *AuthLogger) LogInfo(sessionID, message string, data map[string]interface{}) {
	if al.level > LogLevelInfo {
		return
	}
	al.logWithLevel(LogLevelInfo, sessionID, message, data)
}

// LogWarn logs warning-level messages
func (al *AuthLogger) LogWarn(sessionID, message string, data map[string]interface{}) {
	if al.level > LogLevelWarn {
		return
	}
	al.logWithLevel(LogLevelWarn, sessionID, message, data)
}

// LogError logs error-level messages
func (al *AuthLogger) LogError(sessionID string, err error, context string) {
	if al.level > LogLevelError {
		return
	}
	
	data := map[string]interface{}{
		"error":   al.tokenMasker.MaskSensitiveData(err.Error()),
		"context": context,
	}
	
	al.logWithLevel(LogLevelError, sessionID, "Authentication error occurred", data)
}

// LogAuthFlow logs authentication flow events
func (al *AuthLogger) LogAuthFlow(sessionID, operation string, success bool, duration time.Duration, data map[string]interface{}) {
	if al.level > LogLevelInfo {
		return
	}
	
	status := "SUCCESS"
	if !success {
		status = "FAILED"
	}
	
	logData := map[string]interface{}{
		"operation": operation,
		"status":    status,
		"duration":  duration.String(),
	}
	
	// Add performance metrics for debug mode
	if al.performanceLog {
		logData["duration_ms"] = duration.Milliseconds()
	}
	
	// Merge additional data (with masking)
	if data != nil {
		for k, v := range data {
			if str, ok := v.(string); ok {
				logData[k] = al.tokenMasker.MaskSensitiveData(str)
			} else {
				logData[k] = v
			}
		}
	}
	
	message := fmt.Sprintf("Auth flow: %s %s", operation, status)
	al.logWithLevel(LogLevelInfo, sessionID, message, logData)
}

// LogTokenOperation logs token-related operations with masking
func (al *AuthLogger) LogTokenOperation(sessionID, operation, tokenType string, success bool, metadata map[string]string) {
	if al.level > LogLevelInfo {
		return
	}
	
	status := "SUCCESS"
	if !success {
		status = "FAILED"
	}
	
	data := map[string]interface{}{
		"operation":  operation,
		"token_type": tokenType,
		"status":     status,
	}
	
	// Add masked metadata
	if metadata != nil {
		maskedMetadata := make(map[string]string)
		for k, v := range metadata {
			maskedMetadata[k] = al.tokenMasker.MaskSensitiveData(v)
		}
		data["metadata"] = maskedMetadata
	}
	
	message := fmt.Sprintf("Token operation: %s %s", operation, status)
	al.logWithLevel(LogLevelInfo, sessionID, message, data)
}

// LogConfigValidation logs configuration validation results
func (al *AuthLogger) LogConfigValidation(sessionID string, configType string, valid bool, errors []string) {
	if al.level > LogLevelInfo {
		return
	}
	
	status := "VALID"
	level := LogLevelInfo
	if !valid {
		status = "INVALID"
		level = LogLevelWarn
	}
	
	data := map[string]interface{}{
		"config_type": configType,
		"status":      status,
	}
	
	if len(errors) > 0 {
		// Mask sensitive information in error messages
		maskedErrors := make([]string, len(errors))
		for i, err := range errors {
			maskedErrors[i] = al.tokenMasker.MaskSensitiveData(err)
		}
		data["validation_errors"] = maskedErrors
	}
	
	message := fmt.Sprintf("Config validation: %s %s", configType, status)
	al.logWithLevel(level, sessionID, message, data)
}

// LogPerformanceMetrics logs performance-related metrics (debug mode only)
func (al *AuthLogger) LogPerformanceMetrics(sessionID string, metrics map[string]interface{}) {
	if !al.performanceLog || al.level > LogLevelDebug {
		return
	}
	
	al.logWithLevel(LogLevelDebug, sessionID, "Performance metrics", metrics)
}

// logWithLevel performs the actual logging with level checking and formatting
func (al *AuthLogger) logWithLevel(level LogLevel, sessionID, message string, data map[string]interface{}) {
	if level < al.level {
		return
	}
	
	// Create log entry
	entry := AuthLogEntry{
		Timestamp: time.Now(),
		Level:     level.String(),
		SessionID: al.tokenMasker.MaskSessionID(sessionID),
		Message:   message,
		Data:      data,
	}
	
	// Format and output
	formatted := al.formatLogEntry(entry)
	al.logger.Print(formatted)
}

// AuthLogEntry represents a structured log entry for authentication operations
type AuthLogEntry struct {
	Timestamp time.Time              `json:"timestamp"`
	Level     string                 `json:"level"`
	SessionID string                 `json:"session_id"`
	Message   string                 `json:"message"`
	Data      map[string]interface{} `json:"data,omitempty"`
}

// formatLogEntry formats a log entry for output
func (al *AuthLogger) formatLogEntry(entry AuthLogEntry) string {
	if al.enableDebug {
		// JSON format for debug mode (more detailed)
		jsonData, _ := json.Marshal(entry)
		return string(jsonData)
	}
	
	// Human-readable format for production
	dataStr := ""
	if entry.Data != nil && len(entry.Data) > 0 {
		parts := make([]string, 0, len(entry.Data))
		for k, v := range entry.Data {
			parts = append(parts, fmt.Sprintf("%s=%v", k, v))
		}
		dataStr = fmt.Sprintf(" [%s]", strings.Join(parts, " "))
	}
	
	return fmt.Sprintf("%s [%s] %s%s", 
		entry.Level, 
		entry.SessionID, 
		entry.Message, 
		dataStr)
}

// TokenMasker handles masking of sensitive information in logs
type TokenMasker struct {
	tokenPatterns []*regexp.Regexp
	sessionIDLen  int
}

// NewTokenMasker creates a new token masker
func NewTokenMasker() *TokenMasker {
	patterns := []*regexp.Regexp{
		// JWT tokens (three parts separated by dots) - must come first to avoid generic matching
		regexp.MustCompile(`\b([a-zA-Z0-9\-_]{16,})\.([a-zA-Z0-9\-_]{16,})\.([a-zA-Z0-9\-_]{16,})\b`),
		// Bearer tokens - capture the token part only
		regexp.MustCompile(`(?i)(bearer\s+)([a-zA-Z0-9\-_\.]{16,})`),
		// API keys with various prefixes - capture key and value separately
		regexp.MustCompile(`(?i)((api[_-]?key|token|secret)["\s:=]+)([a-zA-Z0-9\-_\.]{16,})`),
		// Generic long alphanumeric strings that might be tokens (32+ chars)
		regexp.MustCompile(`\b([a-zA-Z0-9\-_]{32,})\b`),
	}
	
	return &TokenMasker{
		tokenPatterns: patterns,
		sessionIDLen:  8, // Show first 8 characters of session ID
	}
}

// MaskSensitiveData masks sensitive information in the input string
func (tm *TokenMasker) MaskSensitiveData(input string) string {
	if input == "" {
		return input
	}
	
	masked := input
	
	// Apply specific masking patterns
	for i, pattern := range tm.tokenPatterns {
		switch i {
		case 0: // JWT tokens (three parts) - must come first
			masked = pattern.ReplaceAllStringFunc(masked, func(match string) string {
				submatches := pattern.FindStringSubmatch(match)
				if len(submatches) >= 4 {
					part1 := tm.maskToken(submatches[1])
					part2 := tm.maskToken(submatches[2])
					part3 := tm.maskToken(submatches[3])
					return part1 + "." + part2 + "." + part3
				}
				return tm.maskToken(match)
			})
		case 1: // Bearer tokens
			masked = pattern.ReplaceAllStringFunc(masked, func(match string) string {
				submatches := pattern.FindStringSubmatch(match)
				if len(submatches) >= 3 {
					prefix := submatches[1]
					token := submatches[2]
					return prefix + tm.maskToken(token)
				}
				return tm.maskToken(match)
			})
		case 2: // API keys with prefixes
			masked = pattern.ReplaceAllStringFunc(masked, func(match string) string {
				submatches := pattern.FindStringSubmatch(match)
				if len(submatches) >= 4 {
					prefix := submatches[1]
					token := submatches[3]
					return prefix + tm.maskToken(token)
				}
				return tm.maskToken(match)
			})
		default: // Generic long strings
			masked = pattern.ReplaceAllStringFunc(masked, func(match string) string {
				return tm.maskToken(match)
			})
		}
	}
	
	return masked
}

// MaskSessionID masks session ID to show only first few characters
func (tm *TokenMasker) MaskSessionID(sessionID string) string {
	if len(sessionID) <= tm.sessionIDLen {
		return sessionID
	}
	
	return sessionID[:tm.sessionIDLen] + "***"
}

// maskToken masks a token while preserving some structure for debugging
func (tm *TokenMasker) maskToken(token string) string {
	if len(token) <= 8 {
		return "***"
	}
	
	// Show first 4 and last 4 characters for debugging purposes
	return token[:4] + "***" + token[len(token)-4:]
}

// parseLogLevel converts string log level to LogLevel enum
func parseLogLevel(level string) LogLevel {
	switch strings.ToLower(level) {
	case "debug":
		return LogLevelDebug
	case "info":
		return LogLevelInfo
	case "warn", "warning":
		return LogLevelWarn
	case "error":
		return LogLevelError
	default:
		return LogLevelInfo
	}
}

// SetLogLevel updates the logger's log level
func (al *AuthLogger) SetLogLevel(level string) {
	al.level = parseLogLevel(level)
}

// SetDebugMode enables or disables debug logging
func (al *AuthLogger) SetDebugMode(enabled bool) {
	al.enableDebug = enabled
	al.performanceLog = enabled && al.level <= LogLevelDebug
}

// IsDebugEnabled returns true if debug logging is enabled
func (al *AuthLogger) IsDebugEnabled() bool {
	return al.enableDebug && al.level <= LogLevelDebug
}

// Flush ensures all log messages are written (no-op for standard logger)
func (al *AuthLogger) Flush() {
	// Standard log.Logger doesn't need explicit flushing
	// This method is provided for interface compatibility
}